Import PDK
Contents
Import PDK#
Import PDK in GDS format#
To import a PDK from GDS format into gdsfactory you need:
GDS file with all the cells that you want to import in the PDK (or separate GDS files, where each file contains a GDS design)
Ideally you also get:
Klayout layer properties files, to define the Layers that you can use when creating new custom Components. This allows you to define the LayerMap that maps Layer_name to (GDS_LAYER, GDS_PuRPOSE)
layer_stack information (material index, thickness, z positions of each layer)
DRC rules. If you don’t get this you can easily build one using klayout.
GDS files are great for describing geometry thanks to the concept of References, where you store any geometry only once in memory.
For storing device metadata (settings, port locations, port widths, port angles …) there is no clear standard.
gdsfactory stores the that metadata in YAML files, and also has functions to add pins
Component.write_gds()saves GDSComponent.write_gds_metadata()save GDS + YAML metadata
[1]:
from typing import Dict, Tuple
# Lets generate the script that we need to have to each GDS cell into gdsfactory
from gdsfactory.component import Component
from gdsfactory.config import PATH
from gdsfactory.technology import lyp_to_dataclass
import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()
c = gf.components.mzi()
c
2023-02-20 17:51:12.454 | INFO | gdsfactory.config:<module>:50 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 6.43.1
2023-02-20 17:51:13.335 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
2023-02-20 17:51:13.342 | INFO | gdsfactory.pdk:activate:206 - 'generic' PDK is now active
mzi: uid d5746a96, ports ['o1', 'o2'], references ['bend_euler_1', 'bend_euler_2', 'bend_euler_3', 'bend_euler_4', 'bend_euler_5', 'bend_euler_6', 'straight_5', 'straight_6', 'straight_7', 'bend_euler_7', 'bend_euler_8', 'straight_8', 'straight_9', 'straight_10', 'sytl', 'syl', 'sxt', 'sxb', 'cp1', 'cp2'], 0 polygons
You can write GDS files only
[2]:
gdspath = c.write_gds("extra/mzi.gds")
2023-02-20 17:51:14.186 | INFO | gdsfactory.component:_write_library:1704 - Wrote to 'extra/mzi.gds'
Or GDS with YAML metadata information (ports, settings, cells …)
[3]:
gdspath = c.write_gds_with_metadata("extra/mzi.gds")
2023-02-20 17:51:14.192 | INFO | gdsfactory.component:_write_library:1704 - Wrote to 'extra/mzi.gds'
2023-02-20 17:51:14.278 | INFO | gdsfactory.component:write_gds_with_metadata:1799 - Write YAML metadata to 'extra/mzi.yml'
This created a mzi.yml file that contains: - ports - cells (flat list of cells) - info (function name, module, changed settings, full settings, default settings)
[4]:
c.metadata.keys()
dict_keys(['name', 'module', 'function_name', 'info', 'info_version', 'full', 'changed', 'default', 'child'])
You can read GDS files into gdsfactory thanks to the import_gds function
import_gds reads the same GDS file from disk without losing any information
[5]:
gf.clear_cache()
c = gf.import_gds(gdspath, read_metadata=True)
c
2023-02-20 17:51:14.298 | INFO | gdsfactory.read.import_gds:import_gds:110 - Read YAML metadata from extra/mzi.yml
mzi: uid 6452c255, ports ['o1', 'o2'], references ['mmi1x2_1', 'mmi1x2_2', 'bend_euler_1', 'straight_length7p0_1', 'bend_euler_2', 'straight_length0p1_1', 'bend_euler_3', 'straight_length2p0_1', 'bend_euler_4', 'straight_length0p1_2', 'bend_euler_5', 'bend_euler_6', 'straight_38238f32_1', 'straight_371f375a_1', 'straight_69b08da7_1', 'bend_euler_7', 'bend_euler_8', 'straight_38238f32_2', 'straight_29ce7f1c_1', 'straight_69b08da7_2'], 0 polygons
[6]:
c2 = gf.import_gds(gdspath, name="mzi_sample", read_metadata=True)
c2
2023-02-20 17:51:15.007 | INFO | gdsfactory.read.import_gds:import_gds:110 - Read YAML metadata from extra/mzi.yml
mzi: uid 11a133e9, ports ['o1', 'o2'], references ['mmi1x2_1', 'mmi1x2_2', 'bend_euler_1', 'straight_length7p0_1', 'bend_euler_2', 'straight_length0p1_1', 'bend_euler_3', 'straight_length2p0_1', 'bend_euler_4', 'straight_length0p1_2', 'bend_euler_5', 'bend_euler_6', 'straight_38238f32_1', 'straight_371f375a_1', 'straight_69b08da7_1', 'bend_euler_7', 'bend_euler_8', 'straight_38238f32_2', 'straight_29ce7f1c_1', 'straight_69b08da7_2'], 0 polygons
[7]:
c2.name
'mzi'
[8]:
c3 = gf.routing.add_fiber_single(c2)
c3
mzi_move_9ec06dec_add_f_29d40b0f: uid fc7428cb, ports ['opt_te_1530_15-mzi-0-0', 'opt_te_1530_15-mzi-1-0', 'opt_te_1530_15-mzi-loopback1', 'opt_te_1530_15-mzi-loopback2'], references ['move_1', 'straight_1', 'straight_2', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'straight_3', 'grating_coupler_elliptical_trenches_3', 'grating_coupler_elliptical_trenches_4'], 0 polygons
[9]:
gdspath = c3.write_gds_with_metadata("extra/pdk.gds")
2023-02-20 17:51:16.419 | INFO | gdsfactory.component:_write_library:1704 - Wrote to 'extra/pdk.gds'
2023-02-20 17:51:16.506 | INFO | gdsfactory.component:write_gds_with_metadata:1799 - Write YAML metadata to 'extra/pdk.yml'
[10]:
gf.labels.write_labels.write_labels_klayout(gdspath, layer_label=gf.LAYER.LABEL)
2023-02-20 17:51:16.514 | INFO | gdsfactory.labels.write_labels:write_labels_klayout:95 - Wrote 0 labels to CSV /home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/pdk.csv
PosixPath('extra/pdk.csv')
add ports from pins#
Sometimes the GDS does not have YAML metadata, therefore you need to figure out the port locations, widths and orientations.
gdsfactory provides you with functions that will add ports to the component by looking for pins shapes on a specific layers (port_markers or pins)
There are different pin standards supported to automatically add ports to components:
PINs towards the inside of the port (port at the outer part of the PIN)
PINs with half of the pin inside and half outside (port at the center of the PIN)
PIN with only labels (no shapes). You have to manually specify the width of the port.
Lets add pins, save a GDS and then import it back.
[11]:
c = gf.components.straight(
decorator=gf.add_pins.add_pins
) # add pins inside the component
c
straight_1514d1a3: uid c1883fda, ports ['o1', 'o2'], references [], 3 polygons
[12]:
gdspath = c.write_gds("extra/wg.gds")
2023-02-20 17:51:17.158 | INFO | gdsfactory.component:_write_library:1704 - Wrote to 'extra/wg.gds'
[13]:
gf.clear_cache()
c2 = gf.import_gds(gdspath)
c2
straight_1514d1a3: uid 302883de, ports [], references [], 3 polygons
[14]:
c2.ports # import_gds does not automatically add the pins
{}
[15]:
c3 = gf.import_gds(gdspath, decorator=gf.add_ports.add_ports_from_markers_inside)
c3
straight_1514d1a3: uid c076b4f2, ports ['o1', 'o2'], references [], 3 polygons
[16]:
c3.ports
{ 'o1': Port (name o1, center [0. 0.], width 0.5, orientation 180, layer PORT, port_type optical), 'o2': Port (name o2, center [10. 0.], width 0.5, orientation 0, layer PORT, port_type optical) }
Foundries provide PDKs in different formats and commercial tools.
The easiest way to import a PDK into gdsfactory is to
have each GDS cell into a separate GDS file
have one GDS file with all the cells inside
Have a KLayout layermap. Makes easier to create the layermap.
With that you can easily create the PDK as as python package.
Thanks to having a gdsfactory PDK as a python package you can
version control your PDK using GIT to keep track of changes and work on a team
write tests of your pdk components to avoid unwanted changes from one component to another.
ensure you maintain the quality of the PDK with continuous integration checks
pin the version of gdsfactory, so new updates of gdsfactory won’t affect your code
name your PDK version using semantic versioning. For example patches increase the last number (0.0.1 -> 0.0.2)
install your PDK easily
pip install pdk_fab_aand easily interface with other tools
To create a Python package you can start from a customizable template (thanks to cookiecutter)
I usually create a python package by running this 2 commands inside a terminal
pip install cookiecutter
cookiecutter https://github.com/joamatab/cookiecutter-pypackage-minimal
It will ask you some questions to fill in the template (name of the package being the most important)
Then you can add the information about the GDS files and the Layers inside that package
[17]:
print(lyp_to_dataclass(PATH.klayout_lyp))
from pydantic import BaseModel
from gdsfactory.typings import Layer
class LayerMap(BaseModel):
CAPACITOR: Layer = (42, 0)
DEEPETCH: Layer = (3, 6)
DEEPTRENCH: Layer = (4, 0)
DICING: Layer = (65, 0)
DRC_EXCLUDE: Layer = (67, 0)
DRC_MARKER: Layer = (205, 0)
DevRec: Layer = (68, 0)
ERROR_MARKER: Layer = (207, 0)
Errors: Layer = (69, 0)
FLOORPLAN: Layer = (64, 0)
FbrTgt: Layer = (81, 0)
GE: Layer = (5, 0)
GENPP: Layer = (26, 0)
GEPPP: Layer = (29, 0)
LABEL_INSTANCES: Layer = (206, 0)
LABEL_OPTICAL_IO: Layer = (201, 0)
LABEL_SETTINGS: Layer = (202, 0)
Lumerical: Layer = (733, 0)
M1: Layer = (41, 0)
M1TILES: Layer = (191, 0)
M2: Layer = (45, 0)
M3: Layer = (49, 0)
METALOPEN: Layer = (46, 0)
MH: Layer = (47, 0)
MONITOR: Layer = (101, 0)
N: Layer = (20, 0)
NOTILE_M1: Layer = (71, 0)
NOTILE_M2: Layer = (72, 0)
NOTILE_M3: Layer = (73, 0)
NP: Layer = (22, 0)
NPP: Layer = (24, 0)
OXIDE_ETCH: Layer = (6, 0)
PDPP: Layer = (27, 0)
PP: Layer = (23, 0)
PPP: Layer = (25, 0)
P_210: Layer = (21, 0)
PinRec: Layer = (1, 10)
PinRecM: Layer = (1, 11)
SHALLOWETCH: Layer = (2, 6)
SHOW_PORTS: Layer = (1, 12)
SILICIDE: Layer = (39, 0)
SIM_REGION: Layer = (100, 0)
SITILES: Layer = (190, 0)
SLAB150: Layer = (2, 0)
SLAB150CLAD: Layer = (2, 9)
SLAB90: Layer = (3, 0)
SLAB90CLAD: Layer = (3, 1)
SOURCE: Layer = (110, 0)
TE: Layer = (203, 0)
TM: Layer = (204, 0)
Text: Layer = (66, 0)
VIA1: Layer = (44, 0)
VIA2: Layer = (43, 0)
VIAC: Layer = (40, 0)
WGCLAD: Layer = (111, 0)
WGNCLAD: Layer = (36, 0)
WGN_Nitride: Layer = (34, 0)
Waveguide: Layer = (1, 0)
XS_BOX: Layer = (300, 0)
XS_GE: Layer = (315, 0)
XS_M1: Layer = (304, 0)
XS_M2: Layer = (399, 0)
XS_MH: Layer = (306, 0)
XS_N: Layer = (320, 0)
XS_NPP: Layer = (321, 0)
XS_OVERLAY: Layer = (311, 0)
XS_OXIDE_M1: Layer = (305, 0)
XS_OXIDE_M2: Layer = (307, 0)
XS_OXIDE_M3: Layer = (311, 0)
XS_OXIDE_MH: Layer = (317, 0)
XS_OXIDE_ML: Layer = (309, 0)
XS_OX_SI: Layer = (302, 0)
XS_P: Layer = (330, 0)
XS_PDPP: Layer = (327, 0)
XS_PPP: Layer = (331, 0)
XS_SI: Layer = (301, 0)
XS_SIN: Layer = (319, 0)
XS_SIN2: Layer = (305, 0)
XS_SI_SLAB: Layer = (313, 0)
XS_VIA1: Layer = (308, 0)
XS_VIA2: Layer = (310, 0)
XS_VIAC: Layer = (303, 0)
class Config:
frozen = True
extra = "forbid"
LAYER = LayerMap()
[18]:
# lets create a sample PDK (for demo purposes only) using GDSfactory
# if the PDK is in a commercial tool you can also do this. Make sure you save a single pdk.gds
sample_pdk_cells = gf.grid(
[
gf.components.straight,
gf.components.bend_euler,
gf.components.grating_coupler_elliptical,
]
)
sample_pdk_cells.write_gds("extra/pdk.gds")
sample_pdk_cells
2023-02-20 17:51:18.476 | INFO | gdsfactory.component:_write_library:1704 - Wrote to 'extra/pdk.gds'
grid_d9610e5f: uid 939d689a, ports ['0_0_o1', '0_0_o2', '1_0_o1', '1_0_o2', '2_0_opt_te_1554_15', '2_0_o1'], references ['0_0', '1_0', '2_0'], 0 polygons
[19]:
sample_pdk_cells.get_dependencies()
[ straight: uid be7068f4, ports ['o1', 'o2'], references [], 1 polygons, grating_coupler_elliptical: uid 0d527a78, ports ['opt_te_1554_15', 'o1'], references [], 32 polygons, bend_euler: uid f3dea00f, ports ['o1', 'o2'], references [], 1 polygons ]
[20]:
# we write the sample PDK into a single GDS file
gf.clear_cache()
gf.write_cells.write_cells(gdspath="extra/pdk.gds", dirpath="extra/gds", recursively=True)
2023-02-20 17:51:19.092 | INFO | gdsfactory.component:_write_library:1704 - Wrote to '/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/grid_d9610e5f.gds'
2023-02-20 17:51:19.093 | INFO | gdsfactory.write_cells:write_cells:202 - Write 'grid_d9610e5f' to /home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/grid_d9610e5f.gds
2023-02-20 17:51:19.095 | INFO | gdsfactory.write_cells:write_cells_recursively:136 - Write 'grid_d9610e5f' to /home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/bend_euler.gds
2023-02-20 17:51:19.096 | INFO | gdsfactory.write_cells:write_cells_recursively:136 - Write 'grid_d9610e5f' to /home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/grating_coupler_elliptical.gds
2023-02-20 17:51:19.098 | INFO | gdsfactory.write_cells:write_cells_recursively:136 - Write 'grid_d9610e5f' to /home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/straight.gds
{ 'grid_d9610e5f': PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/grid_d9610e5f.gds'), 'bend_euler': PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/bend_euler.gds'), 'grating_coupler_elliptical': PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/grating_coupler_elliptical.gds'), 'straight': PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds/straight.gds') }
[21]:
print(gf.write_cells.get_import_gds_script("extra/gds"))
2023-02-20 17:51:19.109 | INFO | gdsfactory.write_cells:get_import_gds_script:93 - Writing 4 cells from PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds')
from pathlib import PosixPath
from functools import partial
import gdsfactory as gf
add_ports_optical = gf.partial(
gf.add_ports.add_ports_from_markers_inside, pin_layer=(1, 0), port_layer=(2, 0)
)
add_ports_electrical = gf.partial(
gf.add_ports.add_ports_from_markers_inside, pin_layer=(41, 0), port_layer=(1, 0)
)
add_ports = gf.compose(add_ports_optical, add_ports_electrical)
gdsdir = PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds')
import_gds = partial(gf.import_gds, gdsdir=gdsdir, decorator=add_ports)
@gf.cell
def bend_euler()->gf.Component:
'''Returns bend_euler fixed cell.'''
return import_gds('bend_euler.gds')
@gf.cell
def grating_coupler_elliptical()->gf.Component:
'''Returns grating_coupler_elliptical fixed cell.'''
return import_gds('grating_coupler_elliptical.gds')
@gf.cell
def grid_d9610e5f()->gf.Component:
'''Returns grid_d9610e5f fixed cell.'''
return import_gds('grid_d9610e5f.gds')
@gf.cell
def straight()->gf.Component:
'''Returns straight fixed cell.'''
return import_gds('straight.gds')
You can also include the code to plot each fix cell in the docstring.
[22]:
print(gf.write_cells.get_import_gds_script("extra/gds", module="samplepdk.components"))
2023-02-20 17:51:19.117 | INFO | gdsfactory.write_cells:get_import_gds_script:93 - Writing 4 cells from PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds')
from pathlib import PosixPath
from functools import partial
import gdsfactory as gf
add_ports_optical = gf.partial(
gf.add_ports.add_ports_from_markers_inside, pin_layer=(1, 0), port_layer=(2, 0)
)
add_ports_electrical = gf.partial(
gf.add_ports.add_ports_from_markers_inside, pin_layer=(41, 0), port_layer=(1, 0)
)
add_ports = gf.compose(add_ports_optical, add_ports_electrical)
gdsdir = PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds')
import_gds = partial(gf.import_gds, gdsdir=gdsdir, decorator=add_ports)
@gf.cell
def bend_euler()->gf.Component:
'''Returns bend_euler fixed cell.
.. plot::
:include-source:
import samplepdk
c = samplepdk.components.bend_euler()
c.plot()
'''
return import_gds('bend_euler.gds')
@gf.cell
def grating_coupler_elliptical()->gf.Component:
'''Returns grating_coupler_elliptical fixed cell.
.. plot::
:include-source:
import samplepdk
c = samplepdk.components.grating_coupler_elliptical()
c.plot()
'''
return import_gds('grating_coupler_elliptical.gds')
@gf.cell
def grid_d9610e5f()->gf.Component:
'''Returns grid_d9610e5f fixed cell.
.. plot::
:include-source:
import samplepdk
c = samplepdk.components.grid_d9610e5f()
c.plot()
'''
return import_gds('grid_d9610e5f.gds')
@gf.cell
def straight()->gf.Component:
'''Returns straight fixed cell.
.. plot::
:include-source:
import samplepdk
c = samplepdk.components.straight()
c.plot()
'''
return import_gds('straight.gds')
Import PDK from other python packages#
You can Write the cells to GDS and use the
Ideally you also start transitioning your legacy code Pcells into gdsfactory syntax. It’s a great way to learn the gdsfactory way!
Here is some advice:
Ask your foundry for the gdsfactory PDK.
Leverage the generic pdk cells available in gdsfactory.
Write tests for your cells.
Break the cells into small reusable functions.
use GIT to track changes.
review your code with your colleagues and other gdsfactory developers to get feedback. This is key to get better at coding gdsfactory.
get rid of any warnings you see.
Foundry PDKs#
You can build PDKs for different foundries. PDKs contain some foundry IP (layer stack, Design Rules) and are available only under and NDA. For accessing the gdsfactory PDK please contact your foundry for access.
Some PDKs are open source and publicly available:
Build your own PDK#
You can create a PDK as a python library using a cookiecutter template. For example, you can use this one.
pip install cookiecutter
cookiecutter https://github.com/joamatab/cookiecutter-pypackage-minimal
Or you can fork the ubcpdk and create new PCell functions that use the correct layers for your foundry. For example.
from pydantic import BaseModel
class LayerMap(BaseModel):
WGCORE = (3, 0)
LABEL = (100, 0)
DEVREC: Layer = (68, 0)
LABEL: Layer = (10, 0)
PORT: Layer = (1, 10) # PinRec
PORTE: Layer = (1, 11) # PinRecM
FLOORPLAN: Layer = (99, 0)
TE: Layer = (203, 0)
TM: Layer = (204, 0)
TEXT: Layer = (66, 0)
LABEL_INSTANCE: Layer = (66, 0)
LAYER = LayerMap()